Repository navigation
fix(agents): disable Cline auto-update so the pinned CLI holds - #4654
Conversation
|
/ok to test 7daa0a6 |
yaoyu-33
left a comment
There was a problem hiding this comment.
Thanks for the thorough write-up and the before/after evidence. The fix is sound for the paths it covers: both the version probe and ClineAgent._env() now set CLINE_NO_AUTO_UPDATE=1, every cline spawn in cline_agent goes through _env() (cline auth and the task), and the three new tests assert real values and fail on main. We found no blockers.
Suggestions
- One
clinecall is still unprotected:cline_agent_deps.sh(outside this diff, see the inline comment onapp.py). Sinceanyswe_clineis called out as covered, it is worth closing here. - README: "every call" is true by default only; see the inline note.
PR hygiene
- The Compatibility section says
cline_version: null"installs@latestat startup".ensure_clinereuses anyclinealready on PATH when no version is requested (setup_cline.py#L51-L52, #L97-L99), so that only installs@lateston a clean machine, and with the updater off an existing install no longer upgrades either. Could you reword that paragraph (the README bullet already says "on a clean machine")? - Validation: the
cline_mathsmoke covers thecline_agentpath; there is no smoke for the AnySWE setup path.
Reviewed with an agent team plus an independent cross-review at 7daa0a6a5. 1 inline suggestion, 1 doc note.
| # Cline otherwise ends every call by spawning a detached `npm update -g cline`, which | ||
| # replaces the pinned cline_version mid-collection and briefly removes the binary from | ||
| # under concurrent spawns. Only the exact value "1" disables it. | ||
| "CLINE_NO_AUTO_UPDATE": "1", |
There was a problem hiding this comment.
Nice fix here. One call is still not covered: cline_agent_deps.sh:33 runs "$DEPS_DIR/bin/cline" --version right after the pinned install, with the server's env and no opt-out. Under this PR's premise that arms npm update -g cline against the same $DEPS_DIR the sandbox later mounts, so the AnySWE recipe can still end up unpinned (the 3.0.55 → 3.0.71 drift), just once per recipe instead of per rollout. Could you prefix it?
| "CLINE_NO_AUTO_UPDATE": "1", | |
| CLINE_NO_AUTO_UPDATE=1 "$DEPS_DIR/bin/cline" --version |
(for cline_agent_deps.sh:33; GitHub can't attach the suggestion there since the file isn't in this diff.)
There was a problem hiding this comment.
Done in c4d6d37: cline_agent_deps.sh now runs CLINE_NO_AUTO_UPDATE=1 "$DEPS_DIR/bin/cline" --version, with a test that every bin/cline call in the script carries it. A/B of that script section on a Linux host with a scratch DEPS_DIR: the old line started npm update -g cline and left DEPS_DIR at 3.0.73; the new one ran no update and stayed at 3.0.55. Details in the PR body.
| the event parser was validated against it, so treat a bump as a deliberate change: raise it, | ||
| re-run the tests and a live eval, then commit). `null` installs `@latest` | ||
| re-run the tests and a live eval, then commit). `null` installs `@latest`. Every `cline` call Gym | ||
| makes sets `CLINE_NO_AUTO_UPDATE=1`, so Cline's self-update cannot replace the pinned install |
There was a problem hiding this comment.
Small wording point: config.env is applied after this key (app.py#L513), so an explicit env: {CLINE_NO_AUTO_UPDATE: "0"} re-enables the updater, which the PR description says is intended. Could the README say "by default" / "unless overridden via env"? A one-line test pinning that precedence would also be nice.
There was a problem hiding this comment.
Done in c4d6d37: the README now says the opt-out is the default and that an explicit env: {CLINE_NO_AUTO_UPDATE: "0"} re-enables it, and test_config_env_can_reenable_auto_update pins that precedence. The Compatibility paragraph is reworded too (cline_version: null installs @latest only on a clean machine).
Every `cline` call, `--version` included, records a pending self-update when npm has a newer release and spawns a detached `npm update -g cline` on exit. The post-install version probe in ensure_cline therefore replaced the pinned 3.0.55 install mid-collection and briefly removed the binary, so a concurrent task spawn failed with FileNotFoundError (HTTP 500, collection aborted). Set CLINE_NO_AUTO_UPDATE=1 (the only value Cline honours) on the version probe and in the env for `cline auth` and the task, without touching the server's own os.environ. Closes #4502 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: qiaochuz <qiaochuz@nvidia.com>
Review follow-up. cline_agent_deps.sh validated the pinned install with a bare `cline --version`, which arms Cline's self-update against $DEPS_DIR, the bundle the AnySWE sandbox mounts. Prefix it with CLINE_NO_AUTO_UPDATE=1. Also say in the README that the opt-out is a default an explicit config.env can override, and pin that precedence with a test. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: qiaochuz <qiaochuz@nvidia.com>
7daa0a6 to
c4d6d37
Compare
|
/ok to test c4d6d37 |
|
🔄 Merge queue validation started! You can track the progress here: https://github.com/NVIDIA-NeMo/Gym/actions/runs/39855678807 |
What does this PR do?
Keeps the Cline CLI at the pinned
cline_versionfor the whole run. Before this change, Gym's owncline --versioncheck started Cline's self-updater. The updater replaced the pinned 3.0.55 installwith npm
latestwhile the run was going, and a task spawned during the swap failed withFileNotFoundError. That surfaced as HTTP 500 and stopped collection.Fixes NVBug 6764103.
Closes #4502
Cause
From
cli-v3.0.55, every Cline CLI invocation,--versionincluded, runsautoUpdateOnStartup()(
apps/cli/src/main.ts). If npm has a newer release, the CLI spawns a detachednpm update -g cline --tag latest --min-release-age=0when it exits (applyDeferredUpdate()inapps/cli/src/index.ts). OnlyCLINE_NO_AUTO_UPDATE=1disables this.The harness added in #2563 never set that variable:
responses_api_agents/cline_agent/setup_cline.py_cline_versionrancline --versionwith theinherited environment. The post-install pin check in
ensure_clinetherefore started the updaterat server startup, with its cwd set to the agent server directory, which matches the npm log in
the NVBug.
ClineAgent._env()built the environment forcline authand the task without it, so everyrollout started the updater again.
While npm reifies the package,
bin/clinedoes not exist. In a local race, 185 of 349 spawns inthat window failed with
FileNotFoundErrorwhile cwd and PATH were intact. Afterwards theinstalled version is no longer the pin (3.0.71).
Fix
_cline_versionpassesenv={**os.environ, "CLINE_NO_AUTO_UPDATE": "1"}to the version probe._env()addsCLINE_NO_AUTO_UPDATE=1next to the other pinnedCLINE_*keys. That overrides aninherited value, and an explicit
config.enventry can still override it, as for every other keythere. This is the same approach
opencode_agenttakes withOPENCODE_DISABLE_AUTOUPDATE.os.environis not modified.npm installis unchanged, because it runs noclinecommand (postinstall.mjsonly links thebinary).
responses_api_agents/anyswe_agent/setup_scripts/cline_agent_deps.shchecked the pinned install witha bare
"$DEPS_DIR/bin/cline" --version, which started the same updater against$DEPS_DIR, thebundle the AnySWE sandbox mounts. It now runs
CLINE_NO_AUTO_UPDATE=1 "$DEPS_DIR/bin/cline" --version(review follow-up). Changing the script also changes the AnySWE runtime-bundle recipe hash, so an
existing, possibly unpinned bundle gets rebuilt.
cline_version. It is a default:config.envis appliedlast, so an explicit
env: {CLINE_NO_AUTO_UPDATE: "0"}re-enables the updater.harness_agentandanyswe_clinerunClineAgent, so its version probe and_env()cover theirclinecalls. The AnySWE install script above is their only otherclinecall.Before the fix
Gym
717c04a3b(mainwhen this PR was opened). The command runs Gym's ownensure_cline("3.0.55")against ascratch npm prefix, with an
npmwrapper first onPATHthat logs argv and cwd:With the real update allowed to run, an asyncio loop spawning
clinethe wayClineAgent._spawndoes (90 s) recorded:
QA testcase
cline_math(README Cline math stack, hosted policy endpoint, no GPU) on Gym717c04a3b. The npm debug logs written during the run:Global
cline --versionafter the run:3.0.71.AnySWE setup path (h100). The Node and Cline section of
cline_agent_deps.shfrommain, run with ascratch
DEPS_DIR:After the fix
Gym
7daa0a6a5(this branch before the rebase; nothing incline_agentoranyswe_agentchanged onmainbetween717c04a3bandc62f9cc67). Sameensure_cline("3.0.55"), then three spawns throughClineAgent._spawnwithClineAgent._env(), with the real update allowed to run:QA testcase
cline_mathon Gym7daa0a6a5. The npm debug logs written during the run show noupdate:Global
cline --versionafter the run:3.0.55.AnySWE setup path, same h100 run with this branch's script:
--versionprinted3.0.55; the onlynpm log is
npm install cline@3.0.55;clineinDEPS_DIR90 s later:3.0.55.Validation
QA testcase
cline_math: before FAIL (FileNotFoundError→/run500, 0/5 rollouts, Cline left at 3.0.71); after 2/2 PASS (5 rollouts, 15 live verifier checks, nonpm update, Cline stays 3.0.55)responses_api_agents/cline_agent/tests+responses_api_agents/anyswe_agent/tests: 122 passed on thebranch rebased onto
c62f9cc67. New tests:"0", with the parent env left untouched._env()with an ambient"0".cline authand the task spawn both carrying"1".config.env"0"still winning.bin/clinecall incline_agent_deps.shcarryingCLINE_NO_AUTO_UPDATE=1.The first three fail on
main(KeyError: 'CLINE_NO_AUTO_UPDATE',KeyError: 'env'), and thescript test fails on
main's script.AnySWE setup path: the
cline_agent_deps.shA/B above. Thecline_mathsmoke covers only thecline_agentpath. No AnySWE rollout smoke ran, because it needs a sandbox provider and containerformatter that h100 does not have.
CLINE_NO_AUTO_UPDATEA/B on the real 3.0.55 binary: unset,0andtrueeach started oneupdate.
1started none.pre-commit runon the changed files: passed.Rollout evidence: the
cline_mathrun above produced 5 real Cline trajectories against a hosted Nemotron endpoint, each with 1–3 tool calls, allfinished_naturally=trueandmask_sample=false, with rewards 1, 1, 1, 1, 0. The single 0 is a wrong answer from the model, not a plumbing failure.Compatibility
Cline no longer self-updates when Gym runs it, by default.
cline_version: nullinstalls@latestonlyon a clean machine.
ensure_clinereuses anyclinealready onPATHwhen no version is requested,and with the updater off, an existing install no longer upgrades itself either. To move to a newer
Cline, bump
cline_version(andCLINE_VERSIONincline_agent_deps.sh). An explicitenv: {CLINE_NO_AUTO_UPDATE: "0"}restores the old behavior. No config or schema changes.🤖 Generated with Claude Code